package com.cz.video.common.storage;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import cn.xuyanwu.spring.file.storage.FileInfo;
import cn.xuyanwu.spring.file.storage.UploadPretreatment;
import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PolicyConditions;
import com.cz.video.common.helper.LoginHelper;
import com.cz.video.common.storage.constants.AliyunConstants;
import com.cz.video.common.storage.constants.OSSConstants;
import com.cz.video.common.storage.dto.OssUploadAuth;
import com.cz.video.common.storage.dto.Statement;
import com.cz.video.common.storage.dto.StsSecurityTokenEntity;
import com.cz.video.common.storage.hander.AliyunStsHandler;
import com.cz.video.common.utils.StringUtils;
import lombok.Getter;
import lombok.Setter;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.function.Consumer;

/**
 * 阿里云 OSS 存储
 */
@Getter
@Setter
public class AliyunOssFileStorage implements  OSSCertificate {

    /* 存储平台 */
    private String platform;
    private String accessKey;
    private String secretKey;
    private String endPoint;
    private String bucketName;
    private String domain;
    private String basePath;

    /**
     * 启用STS
     */
    private Boolean enableSts=false;

    /**
     * 用户文件所在的基本路径
     */
    private String userPath="";

    /**
     * 凭证过期时间（单位秒）
     */
    private long expire;

    /**
     * 文件最大大小（单位MB）
     */
    private long maxSize;

    /**
     * 回调地址
     */
    private String callbackUrl;

    private volatile OSS client;

    /**
     * 阿里云sts处理器
     */
    private AliyunStsHandler stsHandler;

//    private SysOssService ossService;
//
//    private SysOssService getOssService() {
//        if (ossService!=null)
//            return ossService;
//        else
//            ossService= SpringUtils.getBean(SysOssService.class);
//        return ossService;
//    }

    public void setCallbackUrl(String callbackUrl){
        this.callbackUrl=callbackUrl+"/system/oss/callback";
    }



    /**
     * 单例模式运行，不需要每次使用完再销毁了
     * 这里将第三方库原先代码改为了双重检查锁定来确保线程安全
     */
    public OSS getClient() {
      if (client == null) {
        synchronized (AliyunOssFileStorage.class) {
          if (client == null) {
            client = new OSSClientBuilder().build(endPoint,accessKey,secretKey);
          }
        }
      }
      return client;
    }

    /**
     * 仅在移除这个存储平台时调用
     */
    @Override
    public void close() {
        if (client != null) {
            client.shutdown();
            client = null;
        }
        stsHandler.close();
    }

    @Override
    public boolean save(FileInfo fileInfo,UploadPretreatment pre) {
        String newFileKey = basePath + fileInfo.getPath() + fileInfo.getFilename();
        fileInfo.setBasePath(basePath);
        fileInfo.setUrl(domain + newFileKey);

        OSS client = getClient();
        try (InputStream in = pre.getFileWrapper().getInputStream()) {
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentLength(fileInfo.getSize());
            metadata.setContentType(fileInfo.getContentType());
            client.putObject(bucketName,newFileKey,in,metadata);

            byte[] thumbnailBytes = pre.getThumbnailBytes();
            if (thumbnailBytes != null) { //上传缩略图
                String newThFileKey = basePath + fileInfo.getPath() + fileInfo.getThFilename();
                fileInfo.setThUrl(domain + newThFileKey);
                ObjectMetadata thMetadata = new ObjectMetadata();
                thMetadata.setContentLength(thumbnailBytes.length);
                thMetadata.setContentType(fileInfo.getThContentType());
                client.putObject(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes),thMetadata);
            }

            return true;
        } catch (IOException e) {
            client.deleteObject(bucketName,newFileKey);
            throw new FileStorageRuntimeException("文件上传失败！platform：" + platform + "，filename：" + fileInfo.getOriginalFilename(),e);
        }
    }

    @Override
    public boolean delete(FileInfo fileInfo) {
        OSS client = getClient();
        if (fileInfo.getThFilename() != null) {   //删除缩略图
            client.deleteObject(bucketName,fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename());
        }
        client.deleteObject(bucketName,fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename());
        return true;
    }


    @Override
    public boolean exists(FileInfo fileInfo) {
        return getClient().doesObjectExist(bucketName,fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename());
    }

    @Override
    public void download(FileInfo fileInfo,Consumer<InputStream> consumer) {
        OSSObject object = getClient().getObject(bucketName,fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename());
        try (InputStream in = object.getObjectContent()) {
            consumer.accept(in);
        } catch (IOException e) {
            throw new FileStorageRuntimeException("文件下载失败！platform：" + fileInfo,e);
        }

    }

    @Override
    public void downloadTh(FileInfo fileInfo,Consumer<InputStream> consumer) {
        if (StrUtil.isBlank(fileInfo.getThFilename())) {
            throw new FileStorageRuntimeException("缩略图文件下载失败，文件不存在！fileInfo：" + fileInfo);
        }
        OSSObject object = getClient().getObject(bucketName,fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename());
        try (InputStream in = object.getObjectContent()) {
            consumer.accept(in);
        } catch (IOException e) {
            throw new FileStorageRuntimeException("缩略图文件下载失败！fileInfo：" + fileInfo,e);
        }
    }

    @Override
    public OssUploadAuth getUploadAuth(String originalFileName,String platform) {
        OssUploadAuth auth = new OssUploadAuth();

        String suffix= originalFileName.substring(originalFileName.lastIndexOf("."));
        String key = getKey(suffix);
//        //记录oss信息
//        SysOss oss = new SysOss();
//        oss.setPlatform(platform)
//            .setUserId(LoginHelper.getUserIdMust())
//            .setUserType(LoginHelper.getUserType().getUserType())
//            .setFileSuffix(suffix)
//            .setFileName(key.split("/")[key.split("/").length-1])
//            .setType(OSSConstants.IMAGE_FILE_TYPE)
//            .setOriginalName(originalFileName)
//            .setUrl(domain+key);

//        if (getOssService().add(oss)==0)
//            throw new ServiceException("添加oss记录时发生错误");

        OSS ossClient = getClient();
        long expireEndTime = System.currentTimeMillis() + expire * 1000;
        Date expiration = new Date(expireEndTime);
        //策略
        PolicyConditions policyConds = new PolicyConditions();
        //设置大小限制
        policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, maxSize*1024*1024);

        //设置存放地址key
        policyConds.addConditionItem(MatchMode.Exact,PolicyConditions.COND_KEY,key);
        //设置只能上传图片类型
        policyConds.addConditionItem(MatchMode.StartWith,PolicyConditions.COND_CONTENT_TYPE,"image/");


        //生成策略
        String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
        byte[] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8);

        //生成签名
        String postSignature = ossClient.calculatePostSignature(postPolicy);

//        //设置回调信息
//        JSONObject jasonCallback = new JSONObject();
//        jasonCallback.putOnce("callbackUrl", callbackUrl)
//            .putOnce("callbackBody",  //设置回调时获取到的系统信息
//                "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}&id="+oss.getId())
//            .putOnce("callbackBodyType", "application/x-www-form-urlencoded");

        auth.setKey(key)
            .setHost(domain)
            .setAccessKeyId(accessKey)
            .setSignature(postSignature)
            .setExpire(expireEndTime / 1000)
            .setService(OSSConstants.ALI_YUN_SERVICE)
            .setPolicy(BinaryUtil.toBase64String(binaryData));
//            .setCallback(BinaryUtil.toBase64String(jasonCallback.toString().getBytes()));

        return auth;
    }

    private String getKey(String suffix) {
        // 设置文件目录，分为登录和未登录两种设置
        String dir = basePath;
        if (StpUtil.isLogin()){
            dir=dir+userPath+StpUtil.getLoginId()+"/";
        }else {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            dir = dir +sdf.format(new Date())+"/";
        }
        String uuid = UUID.randomUUID().toString();
        return dir+uuid+ suffix;
    }


    @Override
    public String getDownloadCredentials() {
      return null;
    }

  @Override
  public StsSecurityTokenEntity getUserSpaceCertificate() {
      if(StpUtil.getLoginId()==null)
        throw new NotLoginException("未登录","","");
    ArrayList<Statement> statements = new ArrayList<>();
    String basePath= LoginHelper.getUserType().getUserType()+"/"+ LoginHelper.getUserId()+"/";
    //设置 Statement
    Statement statement = new Statement();
    statement.addAction(
        AliyunConstants.AllAction)
      .addResource(
          StringUtils.format(AliyunConstants.OSS_RESOURCE_TEMPLATE,bucketName+"/"+basePath+"*")
      );
    //保存Statement
    statements.add(statement);
    String policy = stsHandler.buildPolicy(statements);
    StsSecurityTokenEntity certificate = stsHandler.getStsSecurityTokenEntity(policy)
      .setBucketName(bucketName).setEndpoint(endPoint);

    //设置回调信息
    certificate.setCallbackUrl(callbackUrl)
        .setCallbackBody("filename=${object}&host=" +StringUtils.format(AliyunConstants.HOST_TEMPLATE,bucketName,endPoint)+"&platform="+platform
            +"&userId="+LoginHelper.getUserId() +"&userType="+LoginHelper.getUserType().getUserType() + "&type="+OSSConstants.IMAGE_FILE_TYPE)
        .setCallbackBodyType("application/x-www-form-urlencoded");

    return certificate.setBasePath(basePath);
  }



}
